<template>
  <div class="relative" @mouseenter="onMouseEnter" @mouseleave="onMouseLeave">
    <div ref="referenceTarget">
      <!-- 具名插槽 - 触发弹层的视图 -->
      <slot name="refence"></slot>
    </div>
    <!-- 气泡展示 -->
    <transition name="slide">
      <div
        ref="contentTarget"
        v-show="isViable"
        class="absolute p-1 z-20 bg-white dark:bg-zinc-900 dark:border-zice-700 border rounded-md"
        :style="contentStyle"
      >
        <!-- 匿名插槽 - 掏出层中展示的内容 -->
        <slot />
      </div>
    </transition>
  </div>
</template>

<script>
// 左上
const PROP_LEFT_TOP = 'left_top'
// 右上
const PROP_RIGHT_TOP = 'right_top'
// 左下
const PROP_LEFT_BOTTOM = 'left_bottom'
// 右下
const PROP_RIGHT_BOTTOM = 'right_bottom'
// 移出延迟时间
const DELAY_TIME = 100

const placementEnum = [
  PROP_LEFT_TOP,
  PROP_RIGHT_TOP,
  PROP_LEFT_BOTTOM,
  PROP_RIGHT_BOTTOM
]
</script>

<script setup>
/**
 * 1.指定所有可选位置的常量 并生成enum
 * 2.通过props控制指定位置
 * 3.获取元素的DOM。创建读取元素尺寸的方法
 * 4.生成气泡的样式对象，用来控制每个位置对应的样式
 * 5.根据prop 计算样式对象
 */
import { processSlotOutlet } from '@vue/compiler-core'
import { ref } from '@vue/reactivity'
import { nextTick, watch } from '@vue/runtime-core'

// props
const props = defineProps({
  // 控制气泡位置，错误给出开发者提示
  placement: {
    type: String,
    default: PROP_RIGHT_BOTTOM,
    validator(val) {
      const result = placementEnum.includes(val)
      if (!result) throw new Error(`只允许以下位置${placementEnum.join('、')}`)
      return result
    }
  }
})
let timeout = ref(null)
// 控制展示匿名插槽
const isViable = ref(false)

// 移入
const onMouseEnter = () => {
  isViable.value = true
  if (timeout) {
    clearTimeout(timeout.value)
  }
}
// 移出
const onMouseLeave = () => {
  timeout.value = setTimeout(() => {
    isViable.value = false
    timeout.value = null
  }, DELAY_TIME)
}
/**
 * 通过dom元素获取元素尺寸
 */
const referenceTarget = ref(null)
const contentTarget = ref(null)

const getElementSize = (target) => {
  if (!target) return {}
  return {
    width: target.offsetWidth,
    height: target.offsetHeight
  }
}

/**
 * 定义气泡位置样式对象
 */
const contentStyle = ref({
  top: 0,
  left: 0
})

/**
 * 监听placement 改变气泡样式
 */
watch(
  isViable,
  (val) => {
    if (!val) return
    nextTick(() => {
      switch (props.placement) {
        // 左上
        case PROP_LEFT_TOP:
          contentStyle.value.top = 0
          contentStyle.value.left =
            -getElementSize(contentTarget.value).width + 'px'
          break
        // 右上
        case PROP_RIGHT_TOP:
          contentStyle.value.top = 0
          contentStyle.value.left =
            getElementSize(referenceTarget.value).width + 'px'
          break
        // 左下
        case PROP_LEFT_BOTTOM:
          contentStyle.value.top =
            getElementSize(referenceTarget.value).height + 'px'
          contentStyle.value.left =
            -getElementSize(contentTarget.value).width + 'px'
          break
        // 右下
        case PROP_RIGHT_BOTTOM:
          contentStyle.value.top =
            getElementSize(referenceTarget.value).height + 'px'
          contentStyle.value.left = contentStyle.value.left =
            getElementSize(referenceTarget.value).width + 'px'
          break
      }
    })
  },
  { immediate: true }
)
</script>
<style lang="scss" scoped>
.slide-enter-active,
.slide-leave-active {
  transition: opacity 0.3s, transform 0.3s;
}

.slide-enter-from,
.slide-leave-to {
  opacity: 0;
  transform: translateY(20px);
}
</style>
